package com.itheima.appserver.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.dubbo.config.annotation.Reference;
import com.google.common.collect.Lists;
import com.itheima.appserver.excepiton.BusinessException;
import com.itheima.appserver.excepiton.ErrorResult;
import com.itheima.appserver.interceptor.UserThreadLocal;
import com.itheima.commons.constants.Constants;
import com.itheima.commons.pojo.domain.UserInfo;
import com.itheima.commons.pojo.entity.PublishInfo;
import com.itheima.commons.pojo.mongo.Publish;
import com.itheima.commons.pojo.mongo.Visitors;
import com.itheima.commons.utils.RelativeDateFormat;
import com.itheima.commons.utils.UploadPicUtil;
import com.itheima.commons.vo.reception.PageResult;
import com.itheima.commons.pojo.domain.QuanZiVo;
import com.itheima.commons.vo.reception.VisitorsVo;
import com.itheima.dubbo.interfaces.*;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.*;

/**
 * @author yaohu
 * @date 2021/12/7
 * @Version 1.0
 * @describe
 */
@Service
public class PublishService {

    @Reference
    private PublishApi publishApi;

    @Reference
    private UserInfoApi userInfoApi;

    @Reference
    private CommentApi commentApi;

    @Reference
    private VisitorsApi visitorsApi;

    @Reference
    private RecommendUserApi recommendUserApi;

    @Autowired
    private IdService idService;

    @Autowired
    private AlbumService albumService;

    @Autowired
    private TimeLineService timeLineService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    /**
     * 发布朋友圈
     *
     * @param textContent   文本信息
     * @param location      位置信息
     * @param latitude      纬度
     * @param longitude     经度
     * @param multipartFile 多媒体信息 图片等
     */
    public void publishMovement(String textContent,
                                String location,
                                String latitude,
                                String longitude,
                                MultipartFile[] multipartFile) throws IOException {
        // 1.处理app传递过来的数据
        if (StrUtil.isBlank(textContent)) {
            throw new BusinessException(ErrorResult.contentError());
        }

        ArrayList<String> medias = new ArrayList<>();
        // 如果有图片，上传oss
        if (ArrayUtil.isNotEmpty(multipartFile)) {
            for (MultipartFile file : multipartFile) {
                String pictureUrl = UploadPicUtil.uploadPicture(file.getBytes(),
                        StrUtil.subAfter(file.getOriginalFilename(), ".", true));
                medias.add(pictureUrl);
            }
        }

        // 2.创建Publish对象
        Publish publish = new Publish();
        // 主键id
        publish.setId(new ObjectId());
        /*
            PID 代表唯一的一个圈子
            我们的大数据系统会根据每个人的行为偏好去推荐圈子数据
            大数据系统在进行推荐的时候 会需要一个唯一ID来代表圈子对象
            但是这个唯一ID要求必须是数值型的 Long Integer
            怎么生成一个唯一的ID ??
            Redis  Zookeeper
         */
        publish.setPid(idService.createId("PUBLISH"));
        // 发布人的ID 当前登录人的ID
        publish.setUserId(UserThreadLocal.getUserId());
        // 圈子的文本内容
        publish.setText(textContent);
        // 圈子的图片
        publish.setMedias(medias);
        // 对谁可见 这个功能并没有实现 所以  随便写
        publish.setSeeType(0);
        // 对谁可见 可见人的列表 没有实现
        publish.setSeeList(Lists.newArrayList());
        // 对谁不可见 不可见人的列表
        publish.setNotSeeList(Lists.newArrayList());
        // 经度
        publish.setLongitude(longitude);
        // 纬度
        publish.setLatitude(latitude);
        // 位置描述 距离?公里
        publish.setLocationName(location);
        // 发布时间
        publish.setCreated(System.currentTimeMillis());

        // 3.保存到发布表中 同时返回发布表中的主键id (不返回也可)
        publishApi.publish(publish);
        // 4.保存圈子id到自己的相册表中
        albumService.save(publish.getId());
        // 5.保存圈子id到自己所有好友的时间线表中
        timeLineService.saveTimeLine(UserThreadLocal.getUserId(), publish.getId());
        // 6.获取刚刚存进去的publish 主要是为了获取publishId
        Publish currentPublish = publishApi.queryCurrentPublishByUserId(UserThreadLocal.getUserId());
        PublishInfo publishInfo = new PublishInfo();
        // rocketMQ传输转换publishId会丢失 只能如此
        publishInfo.setPublishId(currentPublish.getId().toHexString());
        // 7.发送rocketMQ 机器审核
        rocketMQTemplate.convertAndSend("check", publishInfo);
    }

    /**
     * 分页查看好友动态
     *
     * @param page     页码
     * @param pagesize 每页多少条
     * @return 分页查看结果
     */
    public PageResult findFriendMovements(Integer page, Integer pagesize) {

        // 获取好友动态消息 查询自己的时间线表
        List<Publish> publishList = publishApi.findFriendMovements(page, pagesize, UserThreadLocal.getUserId());
        // 新用户朋友圈可能为空 返回空集合
        if (CollUtil.isEmpty(publishList)) {
            return new PageResult();
        }
        // 获取每条信息的发布人id
        List<Long> userIds = CollUtil.getFieldValues(publishList, "userId", Long.class);
        // 根据userId 去 tb_user_info表查询详细信息
        Map<Long, UserInfo> userInfoMap = userInfoApi.findUserInfoByUserIds(userIds);
        // 合并展示朋友圈VO信息
        ArrayList<QuanZiVo> quanZiVoList = publishVODataFormat(publishList, userInfoMap);
        // 返回分页结果集
        return new PageResult(page, pagesize, 0L, quanZiVoList);
    }

    /**
     * 合并展示朋友圈VO信息
     *
     * @param publishList 好友圈子集合
     * @param userInfoMap 每条圈子发布人的信息集合
     * @return 朋友圈VO信息
     */
    private ArrayList<QuanZiVo> publishVODataFormat(List<Publish> publishList, Map<Long, UserInfo> userInfoMap) {
        ArrayList<QuanZiVo> quanZiVoList = new ArrayList<>();
        for (Publish publish : publishList) {
            QuanZiVo quanZiVo = new QuanZiVo();
            // 圈子表 圈子id
            quanZiVo.setId(publish.getId().toHexString());
            // 圈子发布人id
            quanZiVo.setUserId(publish.getUserId());
            // 圈子文本
            quanZiVo.setTextContent(publish.getText());
            // 圈子图片
            quanZiVo.setImageContent(Convert.toStrArray(publish.getMedias()));
            // 发布时间 距现在多久
            quanZiVo.setCreateDate(RelativeDateFormat.format(new Date(publish.getCreated())));

            // 发布人信息
            UserInfo userInfo = userInfoMap.get(publish.getUserId());
            // 头像
            quanZiVo.setAvatar(userInfo.getLogo());
            quanZiVo.setNickname(userInfo.getNickName());
            quanZiVo.setGender(userInfo.getSex() == 1 ? "man" : "woman");
            quanZiVo.setAge(userInfo.getAge());
            // 标签
            quanZiVo.setTags(StrUtil.splitToArray(userInfo.getTags(), ","));

            // 评论相关
            // 点赞数
            Long likeCount = Convert.toLong(redisTemplate.opsForHash().get(Constants.MOVEMENTS_INTERACT_KEY + publish.getId(), Constants.MOVEMENT_LIKE_HASHKEY));
            // 说明当前这个圈子没有人点赞 或者有人点赞但是Redis中没值
            if (ObjectUtil.isNull(likeCount)) {
                // 从MongoDB中进行查询
                likeCount = commentApi.queryLikeCount(publish.getId().toHexString());
                // 把查询到数据给保存到Redis中
                redisTemplate.opsForHash().put(Constants.MOVEMENTS_INTERACT_KEY + publish.getId(), Constants.MOVEMENT_LIKE_HASHKEY, likeCount.toString());
            }
            quanZiVo.setLikeCount(Convert.toInt(likeCount));

            // 评论数
            Long commentCount = Convert.toLong(redisTemplate.opsForHash().get(Constants.MOVEMENTS_INTERACT_KEY + publish.getId(), Constants.MOVEMENT_COMMENT_HASHKEY));
            if (ObjectUtil.isNull(commentCount)) {
                // 从MongoDB中进行查询
                commentCount = commentApi.queryCommentCount(publish.getId().toHexString());
                // 把查询到数据给保存到Redis中
                redisTemplate.opsForHash().put(Constants.MOVEMENTS_INTERACT_KEY + publish.getId(), Constants.MOVEMENT_COMMENT_HASHKEY, commentCount.toString());
            }
            quanZiVo.setCommentCount(Convert.toInt(commentCount));

            // 喜欢数
            Long loveCount = Convert.toLong(redisTemplate.opsForHash().get(Constants.MOVEMENTS_INTERACT_KEY + publish.getId(), Constants.MOVEMENT_LOVE_HASHKEY));
            if (ObjectUtil.isNull(loveCount)) {
                // 从MongoDB中进行查询
                loveCount = commentApi.queryLoveCount(publish.getId().toHexString());
                // 把查询到数据给保存到Redis中
                redisTemplate.opsForHash().put(Constants.MOVEMENTS_INTERACT_KEY + publish.getId(), Constants.MOVEMENT_LOVE_HASHKEY, loveCount.toString());
            }
            quanZiVo.setLoveCount(Convert.toInt(loveCount));

            // 是否点赞
            Boolean isLike = redisTemplate.opsForHash().hasKey(Constants.MOVEMENTS_INTERACT_KEY + publish.getId(), Constants.MOVEMENT_ISLIKE_HASHKEY + UserThreadLocal.getUserId());
            if (!isLike) {
                // 从MongoDB中进行查询
                isLike = commentApi.queryUserIsLike(UserThreadLocal.getUserId(), publish.getId().toHexString());
                if (isLike) {
                    // 把查询到数据给保存到Redis中
                    redisTemplate.opsForHash().put(Constants.MOVEMENTS_INTERACT_KEY + publish.getId(), Constants.MOVEMENT_ISLIKE_HASHKEY + UserThreadLocal.getUserId(), "1");
                }
            }
            quanZiVo.setHasLiked(isLike ? 1 : 0);

            // 是否喜欢
            Boolean isLove = redisTemplate.opsForHash().hasKey(Constants.MOVEMENTS_INTERACT_KEY + publish.getId(), Constants.MOVEMENT_ISLOVE_HASHKEY + UserThreadLocal.getUserId());
            if (!isLove) {
                // 从MongoDB中进行查询
                isLove = commentApi.queryUserIsLove(UserThreadLocal.getUserId(), publish.getId().toHexString());
                if (isLove) {
                    // 把查询到数据给保存到Redis中
                    redisTemplate.opsForHash().put(Constants.MOVEMENTS_INTERACT_KEY + publish.getId(), Constants.MOVEMENT_ISLOVE_HASHKEY + UserThreadLocal.getUserId(), "1");
                }
            }
            quanZiVo.setHasLoved(isLove ? 1 : 0);
            // 距离写死
            quanZiVo.setDistance("距离2KM");

            quanZiVoList.add(quanZiVo);
        }
        return quanZiVoList;
    }

    /**
     * 推荐圈子动态
     *
     * @param page
     * @param pagesize
     * @return
     */
    public PageResult findRecommendMovements(Integer page, Integer pagesize) {

        // 从redis中查看推荐的PID
        String pids = (String) redisTemplate.opsForValue().get(Constants.QUANZI_PUBLISH_RECOMMEND + UserThreadLocal.getUserId());

        List<Publish> publishList = Collections.emptyList();
        // 手动分页(逻辑分页) 大数据推荐系统每隔几分钟计算更新一次推荐PID
        if (StrUtil.isBlank(pids)) {
            // 随机抽取mongodb中的圈子信息
            publishList = publishApi.randomMovements(pagesize);
        } else {
            // 查出的PID是一个字符串，以,分割
            List<String> pidList = StrUtil.split(pids, ",");

            // 计算开始角标  结束角标  结束角标是否>总的数据长度
            // 1页   0     5
            // 2页   5     10
            // 3页   10    15
            // 12条数据  11
            int startIndex = (page - 1) * pagesize;
            // 防止索引越界
            int endIndex = Math.min(startIndex + pagesize, pidList.size());
            // 从pidList中分页截取
            List<String> publishIdList = CollUtil.sub(pidList, startIndex, endIndex);
            // 根据publishIdList查询圈子信息
            publishList = publishApi.findMovementsByPids(Convert.toList(Long.class, publishIdList));
        }
        // 查询发布人的信息
        List<Long> userIdList = CollUtil.getFieldValues(publishList, "userId", Long.class);
        Map<Long, UserInfo> userInfoMap = userInfoApi.findUserInfoByUserIds(userIdList);
        // 合并返回VO
        ArrayList<QuanZiVo> quanZiVoList = publishVODataFormat(publishList, userInfoMap);

        return new PageResult(page, pagesize, 0L, quanZiVoList);
    }

    public QuanZiVo queryById(String publishId) {
        Publish publish = publishApi.queryPublishById(publishId);
        // 根据查出的圈子信息中的userId查询userInfo
        UserInfo userInfo = userInfoApi.findUserInfoByUserId(publish.getUserId());
        // 拼凑QuanZiVo对象
        ArrayList<QuanZiVo> quanZiVoList = publishVODataFormat(CollUtil.toList(publish), MapUtil.builder(userInfo.getUserId(), userInfo).build());
        return quanZiVoList.get(0);
    }

    /**
     * 分页查询用户发布的圈子信息
     *
     * @param userId   用户ID
     * @param page
     * @param pageSize
     * @return
     */
    public PageResult queryAlbumList(Long userId, Integer page, Integer pageSize) {
        // 分页查询自己发布的所有圈子
        List<Publish> publishList = publishApi.findMovementsByUserId(userId, page, pageSize);
        // 查询userInfo信息
        UserInfo userInfo = userInfoApi.findUserInfoByUserId(userId);
        // 拼凑vo
        ArrayList<QuanZiVo> quanZiVoArrayList = publishVODataFormat(publishList, MapUtil.builder(userId, userInfo).build());
        // 返回分页对象
        return new PageResult(page, pageSize, 0L, quanZiVoArrayList);
    }

    /**
     * 谁看过我
     *
     * @return
     */
    public List<VisitorsVo> visitorsTop() {
        // 查询来访用户列表
        List<Visitors> visitorsList = visitorsApi.queryMyVisitor(UserThreadLocal.getUserId(),
                Convert.toLong(redisTemplate.opsForValue().get(Constants.VISITORS_USER_KEY + UserThreadLocal.getUserId())));

        if (CollUtil.isEmpty(visitorsList)) {
            return Collections.emptyList();
        }

        List<Long> visitorUserIds = CollUtil.getFieldValues(visitorsList, "visitorUserId", Long.class);

        Map<Long, UserInfo> userInfoMap = userInfoApi.findUserInfoByUserIds(visitorUserIds);
        ArrayList<VisitorsVo> visitorsVoArrayList = new ArrayList<>();
        for (UserInfo info : userInfoMap.values()) {
            VisitorsVo visitorsVo = new VisitorsVo();

            visitorsVo.setId(info.getUserId());
            visitorsVo.setAvatar(info.getLogo());
            visitorsVo.setNickname(info.getNickName());
            visitorsVo.setGender(info.getSex() == 1 ? "man" : "woman");
            visitorsVo.setAge(info.getAge());
            visitorsVo.setTags(StrUtil.splitToArray(info.getTags(), ","));

            Long score = recommendUserApi.queryScoreByUserId(UserThreadLocal.getUserId(), info.getUserId());
            if (score == null) {
                visitorsVo.setFateValue(0);
            }
            visitorsVo.setFateValue(Convert.toInt(score));

            visitorsVoArrayList.add(visitorsVo);
        }
        return visitorsVoArrayList;
    }
}
